home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Deutsche Edition 1
/
Deutsche Edition 1.iso
/
amok
/
amok_lha
/
amok71.lha
/
Formula
/
Formula.dok
next >
Wrap
Text File
|
1993-08-15
|
21KB
|
541 lines
======================================================================
Dokumentation zu dem Modul Formula V1.0 Stade, den 26.4.92
======================================================================
:Contents. Module to evaluate numeric expressions like
:Contents. "sin(x)/epsilon*(time-7.2E3)"
:Author. Stefan Salewski
:Address. Stefan Salewski, Stolper Weg 3, D-2160 Stade
:Copyright. © 1992 by Stefan Salewski
:Language. Oberon
:Translator. Amiga-Oberon-Compiler V2.14d
:Imports. TurboFiles V2.1 (on this Disk, take Assemblerpart and
:Imports. Documentation from Amok#56
:Imports. RealConversions2 from Amok#58
:Remark. Compile it with options 881 and 68030 set if You use
:Remark. it on an A3000. This speed up the calculations
:Remark. Be careful if You turn off stackcheck, this module
:Remark. uses recursion!
:History. V1.0 3. MAY 1992
Fehlernummern: Formula.error enthält sie, wenn Evaluate() fehlschlägt.
CONST
NoError* = 0;
Overflow* = 1;
DivisionByZero* = 2;
sqrtError* = 3;
lnError* = 4;
arcsinError* = 5;
arccosError* = 6;
artanhError* = 7;
facError* = 8;
rndError* = 9;
entierError* = 10;
powError* = 11;
CompileError* = 12;
Einige benutzte Datentypen, wichtig ist nur Formula:
TYPE
String80 = ARRAY 80 OF CHAR; (* nur für Fehlermeldungen *)
EText = ARRAY 13 OF String80;
WriteProc = PROCEDURE(str:ARRAY OF CHAR);
Formula = RECORD (* Diesen Typ braucht man für Evaluate() *)
error : INTEGER;
END;
Der den obigen Fehlernummern zugeordnete Fehlertext:
CONST
ErrorText=EText('No Error',
'Overflow: ABS(Result) >= MAX(LONGREAL)',
'Division by Zero',
'sqrt(x) only for x>=0',
'ln(x) only for x>0',
'arcsin(x) only for ABS(x)<=1',
'arccos(x) only for ABS(x)<=1',
'artanh(x) only for -1 < x < +1 ',
'fac(x) only for x = {0,1,2,...,170}',
'rnd(x) only for x = {1,2,3,...,MAX(INTEGER)}',
'entier(x) only for ABS(x)<MAX(LONGINT)',
'x^y: if x is negative, y must be an integer',
'Error in Compile()'
);
Die Prozeduren des Moduls alphabetisch sortiert:
PROCEDURE ChangeValue(index: INTEGER; value: LONGREAL);
PROCEDURE Compile(str: ARRAY OF CHAR; VAR formula: Formula): BOOLEAN;
PROCEDURE DefineValue(name: ARRAY OF CHAR; value: LONGREAL;
trash: BOOLEAN;
comment: ARRAY OF CHAR): BOOLEAN;
PROCEDURE DeleteSpaces(VAR str: ARRAY OF CHAR);
PROCEDURE DisposeAllValues;
PROCEDURE Divide(VAR input, name, expression, comment: ARRAY OF CHAR);
PROCEDURE Evaluate(VAR formula: Formula; VAR res: LONGREAL): BOOLEAN;
PROCEDURE FindValue(name: ARRAY OF CHAR; VAR x: LONGREAL;
VAR comment: ARRAY OF CHAR): BOOLEAN;
PROCEDURE GetIndex(name: ARRAY OF CHAR; VAR index: INTEGER): BOOLEAN;
PROCEDURE LoadValues(filename: ARRAY OF CHAR): BOOLEAN;
PROCEDURE RemoveValue(name: ARRAY OF CHAR): BOOLEAN;
PROCEDURE SaveValues(filename: ARRAY OF CHAR): BOOLEAN;
PROCEDURE Split(VAR str1, str2: ARRAY OF CHAR; c: CHAR): BOOLEAN;
PROCEDURE WriteFunctions(p: WriteProc);
PROCEDURE WriteValues(p: WriteProc);
Inhalt:
-------
Aufgabe dieses Moduls ist es, mathematische Formeln, die als String
vorliegen, zu berechnen. Dadurch wird es recht einfach mathematische
Programme wie Funktionsplotter, "Taschenrechner", Programme die
nummerisch Integrieren, Differentialgleichungen nummerisch lösen, usw.
zu schreiben. Man braucht im Prinzip nur die Formel in Form eines
Strings einzulesen, sie mit Compile() zu überprüfen und kann sie dann
(eventuell mehrmals mit geänderten Zahlenwerten) mit Evaluate()
berechnen. Diese Berechnung geht recht schnell, auf einem A3000 dauerd
die Berechnung einer durchschnittlichen Formel nur ca. eine
tausendstel Sekunde. (Auf einem Rechner ohne Arithmetikprozessor
dauert die Berechnung natürlich etwas länger, da diese Rechner mit
LONGREAL-Zahlen nur langsam Rechnen können.)
COPYRIGHT
---------
Dieses Modul befindet sich nicht in der Public Domain! Alle Rechte an
diesem Modul liegen beim Autor. Diese Version von FORMULA darf nur in
Verbindung mit einer unveränderten AMOK-Diskette verbreitet werden.
Die Aufnahme von FORMULA auf eine andere Diskettenserie oder irgendein
sonstiger Vertrieb dieses Moduls ist nur mit meiner schriftlichen
Genehmigung erlaubt. Insbesondere sind Modifikationen des Moduls oder
der Dokumentation, der Abdruck oder die Übersetzung und jede andere
Vervielfältigung von Modul und Dokumentation untersagt. Grundsätzlich
darf für eine Diskette auf der sich Formula befindet nur der Preis der
Leerdiskette zuzüglich einer geringen Kopiergebühr verlangt werden.
(In der Regel kostet eine FD-Diskette DM 1.60 + Porto) Der Autor
behält sich das Recht vor, einzelnen Firmen oder Personen das Recht
auf Verbreitung dieses Produktes zu untersagen!
Dieses Modul ist FREEWARE. Das heißt es kann (und sollte) für die
Entwicklung eigener Rechenprogramme benutzt werden, solange die damit
erzeugten Programme nicht kommerziell vermarktet werden. Wer Formula
für kommerzielle Produkte verwenden will muß vorher meine schriftliche
Genehmigung einholen.
Meine Anschrift:
Stefan Salewski
Stolper Weg 3
D-2160 Stade
(Germany)
Einführung
----------
Befor ich auf die einzelnen Prozeduren des Moduls eingehe möchte ich
einen kurzen Überblick über das grundlegende Konzept des Moduls geben.
Dieses Modul kann praktisch jede mathematisvhe Formel berechnen. Dabei
dürfen die Formeln auch durch Namen bezeichnete Konstanten und
Variablen enthalten. Ausdrüche wie etwa
1.23*sin(4.5)+Pi/epsilon
arcsin(3/2)-2.7E3^3.2
können berechnet werden.
Die Ausdrücke dürfen folgendes enthalten:
Die mathematischen Operatoren +,-,*,/,^
Zahlenkonstanten wie 1.23E-5, 3.14, 2
Elementare Funktionen wie sin, exp, arctan
Klammern (),[],{}
Werte (engl. Values)
(Leerzeichen in mathematischen Ausdrücken sind nicht erlaubt.)
Operatoren
----------
Es existieren 5 mathematische Operatoren:
Symbol Bedeutung Priorität
----------------------------
+ Plus 1
- Minus 1
* Mal 2
/ Durch 2
^ Hoch 3
Mal und Durch haben also Vorrang vor Plus und Minus. Der
Potenzoperator ^ hat Vorrang vor den übrigen vier Operatoren. (Dies
ist allgemein üblich und unter den Begriff "Punkt- vor Strichrechnung"
bekannt.) Um andere Prioritäten festzulegen können selbstverständlich
Klammern benutzt werden. Neben () können auch [] und {} benutzt
werden. Das Weglassen des Multiplizieroperators ist nicht erlaubt. Man
darf also statt 4*Pi*e0 nicht 4Pie0 schreiben. (Manchmal findet man in
Büchern Ausdrücke wie "1/4 Pi e0". Gemeint ist damit "1/(4*Pi*e0)".
Den weggelassenen Multiplikationsoperatoren wird also eine höhere
Priorität als dem Divisionsoperator / zugeordnet. Da das Weglassen von
Operatoren nicht erlaubt ist und Mal und Durch gleiche Priorität haben
muß man also "1/(4*pi*e0)" schreiben.)
Elementare mathematische Funktionen
-----------------------------------
In wesentlichen handelt es sich hierbei um bekannte Funktionen, die
von dem mathematischen Koprozessor oder der MathIEEEDoubTrans.library
berechnet werden. Argumente oder Ergebnisse von Winkelfunktionen wie
sin() oder arctan() sind immer auf Radiant bezogen.
DegToRad()
----------
Wandelt einen in Grad angegebenen Winkel in Radiant um. Das gleiche
erreicht man durch "*Pi/180"
RND()
-----
Produziert Zufallszahlen. Das Argument muß eine GANZE Zahl zwischen 1
und MAX(INTEGER) sein. RND(n) liefert eine Zufallszahl zwischen 0 und
(n-1). (RND(6)+1) simuliert also einen Würfel.
RadToDeg()
----------
Wandelt einen in Radiant angegebenen Winkel in Grad um.
"RadToDeg(x)" entspricht also "x/Pi*180"
abs()
-----
Ergibt den Absolutwert oder Betrag des Argumentes.
arccos()
--------
Die Arcus-Cosinus-Funktion. Inverse Funktion des Cosinus.
Der Betrag des Argumentes darf nicht größer als eins sein.
arcsin()
--------
Die Arcus-Sinus-Funktion. Inverse Funktion des Sinus.
Der Betrag des Argumentes darf nicht größer als eins sein.
arctan()
--------
Die Arcus-Tanges-Funktion. Inverse Funktion des Tangens.
artanh()
--------
Die Arcus-Tangens-Hyperbolicus-Funktion. Inverse Funktion des Tanges-
Hyperbolicus.
ceil()
------
Liefert die nächste ganze Zahl die größer oder gleich dem Argument
ist. Libraryfunktion, Name kommt von ceiling, engl. für Decke.
cos()
-----
Cosinus
cosh()
------
Cosinus-Hyperbolicus
entier()
--------
Liefert die nächste ganze Zahl, die kleiner oder gleich dem Argument
ist. Die Berechnung erfolgt über die OBERON-Funktion ENTIER(), daher
muß der Betrag des Arguments kleiner als MAX(LONGINT) sein. Eigentlich
sollte man lieber floor() benutzen, da man dann diese
Bereicheinschränkung nicht beachten muß.
exp()
-----
Exponentialfunktion zur Basis e=2.7...
fac()
-----
Fakultät. Das Argument muß eine ganze Zahl zwischen 0 und 170 sein.
floor()
-------
Liefert die nächste ganze Zahl kleiner oder gleich dem Argument.
Libraryfunktion, Name kommt von floor engl. Boden, siehe auch ceil().
id()
----
Identität. id(x)=x
int()
-----
Liefert die nächste ganze Zahl, die dichter bei Null liegt als das
Argument oder mit dem Argument identisch ist.
jump()
------
(Heaviside'sche) Sprungfunktion. Liefert 1 für Argumente die größer
als Null sind, sonst Null. Diese Funktion ist recht nützlich um
bestimmte Stellen eines Funktionsgraphen auszublenden. In der
Elektronik kommt sie unter dem namen E() vor, ich habe sie jump()
genannt um Verwechslungen mit der Exponentialfunktion zu verhindern
und um den Namen "E" für Zahlenwerte freizuhalten.
ln()
----
Der natürliche Logarithmus zur Basis e=2.7...
log()
-----
Der Zehnerlogarithmus (dekadischer Logarithmus) zur Basis 10.
log() ist identisch mit log10()
log2()
------
Logarithmus zur Basis 2.
round()
-------
Rundung auf die nächste ganze Zahl.
round(x) entspricht floor(x+0.5).
sin()
-----
Sinusfunktion. Argumente sind wie bei allen Winkelfunktionen auf
Radiant bezogen.
sinh()
------
Sinus-Hyperbolicus
sqr()
-----
Quadrat. sqr(x)=x*x
sqrt()
------
Quadratwurzel. sqrt(x*x)=abs(x). Das Argument darf nicht negativ sein.
tan()
-----
Tangens.
tanh()
------
Tangens Hyperbolicus
tentox()
--------
Ergibt 10^x
twotox()
--------
Ergibt 2^x
Werte (engl. Values)
--------------------
Werte ist der Oberbegriff für Variablen und Konstanten. Wie schon
gesagt darf ein mathematischer Ausdruck beliebige Namen enthalten.
Statt 3.14 kann man in der Formel dann "Pi" einsetzen oder statt
2.9979E8 "Lichtgeschwindigkeit". Dadurch wird die Lesbarkeit von
Formeln stark verbessert ohne die Effizienz der Berechnung zu
verringern. Die Werte können, nachden sie definiert worden sind, in
eine ASCII-Datei abgespeichert und aus dieser wieder eingelesen
werden.
Nun zu den Prozeduren im einzelnen:
-----------------------------------
PROCEDURE DefineValue(name: ARRAY OF CHAR;
value: LONGREAL;
trash: BOOLEAN;
comment: ARRAY OF CHAR): BOOLEAN;
Mit dieser Prozedur definiert man neue Werte oder ändert alte. name
ist der Name des Wertes, der neu definiert oder geändert werden soll,
value gibt den zugehörigen Zahlenwert an. Der Name darf fast alle über
die Tastatur erreichbaren Zeichen enthalten, die Operatoren +,-
,*,/,^,:,= und Klammern sind allerdings nicht erlaubt. Außerdem darf
der Name nicht mit einer Ziffer beginnen. Er darf aber Ziffern
enthalten und auch mit einer Ziffer enden. Natürlich darf man für
Werte nicht Namen verwenden, die schon für elementare Funktionen (sin,
cos usw.) vergeben sind. Das Argument trash gibt an, ob dieser Wert
später eventuell gespeichert werden soll oder nicht. Ist trash TRUE,
so wird der Wert nicht gespeichert, ist trash gleich FALSE, so kann er
später gespeichert werden. comment ist ein beliebiger Kommentar, der
ebenfalls mit abgespeichert werden kann und z.B. angibt, auf welches
Einheitensystem sich ein Wert bezieht oder ähnliches. Konnte der neue
Wert definiert werden, so liefert die Prozedur TRUE als Resultat,
anderenfalls FALSE. (Auch bei den folgenden Prozeduren bedeutet das
Resultat TRUE, daß die betreffende Operation erfolgreich durchgeführt
werden konnte) Momentan kann das Modul 2048 Werte verwalten.
PROCEDURE SaveValues(filename: ARRAY OF CHAR): BOOLEAN;
-------------------------------------------------------
Diese Prozedur speichert alle momentan existierenden Werte in eine
Datei ab. Das betrifft die Werte, die entweder zuvor mit LoadValues()
eingelesen oder mir DefineValue() definiert worden sind. Werte die
zwar mit DefineValue() definiert wurden, bei denen aber trash=TRUE
war, werden nicht abgespeichert. So kann man verhindern, daß
irgendwelche Hilfsvariablen, die man eh nie wieder braucht, in der
Datei Platz wegnehmen! Die Datei ist eine Textdatei, die mit jedem
ASCII-Editor bearbeitet werden kann. Jede Zeile der Datei definiert
einen Wert und sieht folgendermaßen aus:
<name> = <wert> ; [ Kommentar ]
Beispiel:
Lichtgeschwindigkeit = 2.9979E8 ; in m/s
Die Datei darf bis zu 2048 Zeilen haben, eine Zeile darf bis zu 256
Zeichen lang sein.
PROCEDURE LoadValues(filename: ARRAY OF CHAR): BOOLEAN;
-------------------------------------------------------
Mit dieser Prozedur kann man die gespeicherten Werte wieder einlesen
und sie dann in Formeln benutzen.
PROCEDURE Compile(str: ARRAY OF CHAR; VAR formula: Formula): BOOLEAN;
---------------------------------------------------------------------
Compile() und Evaluate() sind die grundlegenden Prozeduren dieses
Moduls. Compile() bekommt den mathematischen Ausdruck als String
übergeben, überprüft ihn und wandelt ihn in den Datentyp Formula um.
Die Variable von Typ Formula wird danach der Prozedur Evaluate()
übergeben um die Berechnung (eventuell mehrfach mit veränderten
Werten) durchzuführen. Gibt Compile() FALSE zurück, so enthält der
String keine korrekte Formel, beispielsweise Klammerfehler oder
undefinierte Werte, die man zuvor mit DefineValue() definieren muß.
Wird TRUE zurückgegeben, kann man als nächstes Evaluate() aufrufen.
PROCEDURE Evaluate(VAR formula: Formula; VAR res: LONGREAL): BOOLEAN;
---------------------------------------------------------------------
Diese Prozedur berechnet die zuvor mit Compile() bearbeitete Formel.
Wird TRUE zurückgegeben, so enthält res das Resultat. Wird dagegen
FALSE zurückgegeben, so ist ein Rechenfehler aufgetreten, etwa eine
Division durch Null, und res daher ungültig. formula.error enthält
dann die entsprechende Fehlernummer, und man kann mit
io.WriteString(ErrorText(formula.error) eine Fehlermeldung ausgeben.
PROCEDURE GetIndex(name: ARRAY OF CHAR; VAR index: INTEGER): BOOLEAN;
---------------------------------------------------------------------
Wie oben gesagt kann man mit DefineValue() Werte ändern. Dieser Weg
ist aber nicht sehr schnell. Muß man einen Wert sehr oft verändern,
beispielsweise bei einem Funktionsplotter, so gibt es einen besseren
Weg. GetIndex(name,index) liefert den index des Wertes mit dem Namen
name. Hat man den index ermittelt, so kann man den Wert mit
ChangeValue() sehr schnell verändern. GetIndex() gibt TRUE zurück,
wenn der Index ermittelt werden konnte. Wird FALSE zurückgegeben, so
existert ein Wert mit dem Namen name nicht und index ist ungültig.
ACHTUNG: Durch Aufruf von DisposeAllValues() oder RemoveValue(name)
wird der index ungültig und muß mit GetIndex() neu ermittelt
werden. (Die übrigen Prozeduren machen den index nicht
ungültig)
PROCEDURE ChangeValue(index: INTEGER; value: LONGREAL);
-------------------------------------------------------
Hat man den index mit GetIndex() ermittelt, so kann man mit
ChangeValue() einen Wert sehr schnell verändern. index ist der
(mit GetIndex()) ermittelte index, value der neue Zahlenwert.
PROCEDURE FindValue(name: ARRAY OF CHAR; VAR x: LONGREAL;
VAR comment: ARRAY OF CHAR): BOOLEAN;
---------------------------------------------------------
Dies ist eine Prozedur, die man wohl nicht so oft benötigen wird. Mit
ihr kann man nach einem Wert mit Namen name suchen. Existiert dieser
Wert, so gibt die Prozedur TRUE zurück und x enthält den zugehörigen
Zahlenwert und comment den eventuell vorhandenen Kommentar.
PROCEDURE RemoveValue(name: ARRAY OF CHAR): BOOLEAN;
----------------------------------------------------
Hiermit kann man einen nicht mehr benötigten Wert vernichten. Er kann
dann nicht mehr benutzt werden und wird auch nicht mehr abgespeichert.
Eine zuvor mit Compile() erzeugte Formel, die diesen Wert enthält
ist hiernach ungültig.
PROCEDURE DisposeAllValues;
---------------------------
Löscht alle Werte. Hierdurch werden alle Formeln ungültig!
Bevor man sie wieder mit Evaluate() berechnen kann müssen sie neu
mit Compile() vorbereitet werden werden.
PROCEDURE WriteValues(p: WriteProc);
------------------------------------
Ruft mit allen Werten die Prozedur p auf. p muß eine Prozedur sein,
die als Parameter einen String übergeben bekommt, etwas
io.WriteString().
PROCEDURE WriteFunctions(p: WriteProc);
---------------------------------------
Ruft mit allen elementaren Funktionen die Prozedur p auf. p muß eine
Prozedur sein, die als Parameter einen String übergeben bekommt, etwas
io.WriteString().
Die letzen drei Prozeduren dienen nur zur Stringbearbeitung. Sie
werden von den vorherigen Prozeduren benutzt und können eventuell von
Nutzen sein, auch wenn sie mit den Berechnungen nichts zu tun haben.
PROCEDURE DeleteSpaces(VAR str: ARRAY OF CHAR);
-----------------------------------------------
Dies ist, wie auch die beiden folgenden Prozeduren eine reine
Stringbearbeitungsprozedur. Es werden in str alle Leerzeichen, die an
Anfang oder Ende des Strings stehen entfernt. Aus " Hallo Stefan "
wird also "Hallo Stefan".
PROCEDURE Split(VAR str1, str2: ARRAY OF CHAR; c: CHAR): BOOLEAN;
-----------------------------------------------------------------
(* splits str1 at position determined by c in str1 and str2 *)
(* If str1="" then str1:="" and str2:="" *)
(* IF c is the last Char in str1 then str1:=str1-c and str2:="" *)
(* IF c is the first Char in str1 then str1:="" and str2:= str2-c*)
(* RETURNS TRUE if (c is in str1) *)
Hiermit kann man einen String in zwei Teile aufspalten. Gespalten wird
an der Position, an der das Zeichen c das erste mal auftaucht. Wenn c
nicht in str1 vorhanden ist wird FALSE zurückgegeben.
PROCEDURE Divide*(VAR input,name,expression,comment:ARRAY OF CHAR);
-------------------------------------------------------------------
(* input= "pi = 2*arcsin(1) ; ~3.14"
==> name= "pi"
==> expression= "2*arcsin(1)"
==> comment= "~3.14"
*)
Der String Input wird in name, expression und comment aufgespalten.
Was vor dem Gleichheitszeichen steht wird als name angesehen, was
hinter dem Semikolon steht als Kommentar.
Zum Schluß noch ein paar Bemerkungen.
-------------------------------------
Der Quelltext wird zwar mitgeliefert, er dient aber hauptsächlich
dokumentatorischen Zwecken und sollte nicht verändert werden. Wer
Programme für einen A3000 entwickelt kann die Compileroptionen 68030
und 882 setzen. Dadurch werden die Programme deutlich schneller,
laufen dann aber auf Rechnern ohne Koprozessor nicht mehr. Dieses
Modul braucht etwas Stack, da mathematische Ausdrücke rekursiv sind
und auch rekursiv berechnet werden. Wenn es auf extreme
Geschwindigkeit ankommt kann man dieses Modul auch ohne
Überprüfungscode compilieren, wenn man allerdings den StackCheck
abschaltet muß man sehr vorsichtig sein. Das Modul Formula fragt bei
Programmstart mit Hilfe von OberonLib.StackCheck() ab, ob noch
mindestens 10 KByte Stack frei sind. Das ist aber keine Garantie dafür
daß nicht doch ein Stacküberlauf stattfinden kann. 14 kByte Stack
reichen scheinbar aus, etwas mehr ist aber besser!
Natürlich kann man mit mehreren Formeln gleichzeitig rechnen.
Definiert man sich etwa die drei Variablen
VAR f1,f2,f3:Formula.Formula;
so kann man drei verschiedene Formeln mit
Compile() vorbereiten und dann abwechselnd mit Evaluate() auswerten.
Das Programm kann momentan 2048 Werte verwalten.
Eine Formel darf maximal 1024 Elemente enthalten. Als
Element gilt dabei jeweils ein Operator, ein Wert, eine
elementare Funktion, eine Zahlenkonstante oder eine Klammer.
"sin(Pi/123)*Epsilon" enthält also nur acht Elemente. Ich
denke, daß man diese Grenzen wohl nie erreichen wird, aber
bei Bedarf können diese Werte natürlich im Quelltext erhöht
werden. Die Berechnungen erfolgen mit LONGREAL-Zahlen, so daß
(Zwischen)-Ergebnisse bis zu 1.0E308 groß werden dürfen. Die
Rechengenauigkeit ist mindestens 14-stellig.
Stefan Salewski, 26.4.92